package be.destin.abdera;

import java.lang.reflect.InvocationTargetException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.StringTokenizer;
import java.util.TreeSet;

import org.apache.abdera.factory.Factory;
import org.apache.abdera.i18n.iri.IRI;
import org.apache.abdera.model.Content;
import org.apache.abdera.model.Entry;
import org.apache.abdera.model.Person;
import org.apache.abdera.model.Text;
import org.apache.abdera.protocol.server.RequestContext;
import org.apache.abdera.protocol.server.TargetType;
import org.apache.abdera.protocol.server.context.ResponseContextException;
import org.apache.abdera.util.Constants;
import org.apache.log4j.Logger;
import org.openrdf.model.Value;

import be.destin.beans.Reflector;
import be.destin.memolog.UsageCount;
import be.destin.memolog.UsageLogger;
import be.destin.memolog.UsageMode;
import be.destin.rdf.changes.Status;
import be.destin.skos.core.Concept;
import be.destin.skos.core.ConceptIdentifier;
import be.destin.skos.core.ConceptScheme;
import be.destin.skos.core.Count;
import be.destin.skos.core.NoScheme;
import be.destin.skos.core.Notation;
import be.destin.skos.core.NotationScheme;
import be.destin.skos.core.Predicate;
import be.destin.skos.core.ReferringApplication;
import be.destin.skos.core.SchemeInterface;
import be.destin.skos.core.SchemeUsage;
import be.destin.skos.core.SkosManager;
import be.destin.skos.core.Term;
import be.destin.skos.core.TermFormat;
import be.destin.skos.core.TermList;
import be.destin.skos.core.UrlList;
import be.destin.skos.html.SkosHtmlManager;
import be.destin.util.Util;

public class ConceptCollectionAdapter extends AbstractCorrectedCollectionAdapter<Concept> {

	/*
  void 	deleteEntry(String resourceName, RequestContext request)
          Delete an entry
 String 	getAuthor(RequestContext request)
          The author of this collection.
 List<Person> 	getAuthors(Employee entry, RequestContext request)
          Get the authors for an entry.
 Object 	getContent(Employee entry, RequestContext request)
          Get the content for the entry.
 Iterable<Employee> 	getEntries(RequestContext request)
          Get the listing of entries requested
 Employee 	getEntry(String resourceName, RequestContext request)
          Get a specific entry
 String 	getId(Employee entry)
          Gets the UUID for the specified entry.
 String 	getId(RequestContext request)
          A unique ID for this feed.
 String 	getName(Employee entry)
          Get the name of the entry resource (used to construct links)
 String 	getTitle(Employee entry)
          Get the title fo the entry
 String 	getTitle(RequestContext request)
          The title of our collection.
 Date 	getUpdated(Employee entry)
          Get the value to use in the atom:updated element
 Employee 	postEntry(String title, IRI id, String summary, Date updated, List<Person> authors, Content content, RequestContext request)
          Create a new entry
 void 	putEntry(Employee employee, String title, Date updated, List<Person> authors, String summary, Content content, RequestContext request)
          Update an entry.

	 */

	/** log4j category */
	private static Logger log = Logger.getLogger(ConceptCollectionAdapter.class);

	NoScheme scheme = null;

	SkosManager skosManager = null;

	/**
	 * @param scheme
	 */
	public ConceptCollectionAdapter(SkosManager skosManager, NoScheme scheme) {
		super();
		this.skosManager = skosManager;
		this.scheme = scheme;
		this.setHref(scheme.getAbout());
	}

	@Override
	public String getTitle(RequestContext context) {
		String language = getUserLanguage(context);
		return scheme.getTitle(language);
	}

	@Override
	public void deleteEntry(String about, RequestContext context)
			throws ResponseContextException {
		if (about != null && (scheme instanceof ConceptScheme) ) {
			Concept aConcept = skosManager.getScheme_Concept(about,scheme, null, null);
			if (aConcept != null) {
				((ConceptScheme)scheme).removeConcept(aConcept);
			}
		}
	}

	@Override
	public List<Concept> getListEntries(RequestContext context)
			throws ResponseContextException {
		return scheme.getAllConcepts();
	}

    @Override
	public Concept getEntry(String about, RequestContext context)
			throws ResponseContextException {
		return (skosManager.getScheme_Concept(about,scheme, null, null));
	}

	/**
	 * Add the details to an entry
	 * 
	 * @param request The request context
	 * @param e The entry
	 * @param feedIri The feed IRI
	 * @param entryObj
	 */
	@Override
	public String addEntryDetails(RequestContext request, Entry e, IRI feedIri, Concept aConcept)
			throws ResponseContextException {
		/*
		 *       (atomAuthor*
		 *        &amp; atomCategory*
		 *        &amp; atomContent?
		 *        &amp; atomContributor*
		 *        &amp; atomId
		 *        &amp; atomLink*
		 *        &amp; atomPublished?
		 *        &amp; atomRights?
		 *        &amp; atomSource?
		 *        &amp; atomSummary?
		 *        &amp; atomTitle
		 *        &amp; atomUpdated
		 *        &amp; extensionElement*)
		 */
		String language = getUserLanguage(request);
		//String link = super.addEntryDetails(request, e, feedIri, aConcept);
		HashMap<String,String> param = new HashMap<String,String>();
		param.put("collection", aConcept.getInScheme().getAbout());
		param.put("entry", aConcept.getAbout());
		String link = request.urlFor(TargetType.TYPE_ENTRY, param);

		if (link!=null && !link.isEmpty()) {
			e.addLink(link, "self");
/* duplicated code because of bad link generation */
			e.addLink(link, "edit");
		}
		e.setId(getId(aConcept));
		Term currentLabel = aConcept.getPrefLabelTerm(language, false);
		Text currentTitle = e.setTitle(currentLabel.getLabel());
		String aLang = currentLabel.getLang();
		if (aLang !=null && aLang != TermList.anyLanguage) {
			currentTitle.setLanguage(aLang);
		}

		String currentUser = AbstractCorrectedWorkspaceProvider.getCurrentUser(request);
		UsageLogger aUsageLogger = MemoProvider.getNewUsageLogger (request, SkosHtmlManager.askosiDisplayApplication, aConcept.getScheme_About(),UsageMode.UPDATE);
		UsageCount total = aUsageLogger.count();
		if (total == null) {
			e.setUpdated(getUpdated(aConcept));
		} else {
			Date date = total.getMinDate();
			if (date.getTime() < Long.MAX_VALUE) e.setUpdated(date);
			else e.setUpdated(getUpdated(aConcept));
/*
			date = total.getMaxDate();
			if (date.getTime() > 0) e.setPublished(date);
			else e.setPublished(e.getUpdated());
*/
		}
		List<Person> authors = getAuthors(aConcept, request);
		if (authors != null) {
			for (Person a : authors) {
				e.addAuthor(a);
			}
		}
		String rights = aConcept.getInScheme().getRights(language);
		if (rights != null && !rights.isEmpty()) {
			e.setRights(rights);
		}

		Text t = getSummary(aConcept, request);
		if (t != null) {
			e.setSummaryElement(t);
		}
		/* end of duplicated code because of bad link generation */

		if (aConcept.getBroader() != null) {
			for (String anAbout : aConcept.getScheme_AboutBroaderTransitive()) {
				Concept btConcept = skosManager.getScheme_Concept(anAbout,scheme, null, null);
				if (btConcept != null) {
					String aLabel = btConcept.getPrefLabel(language, false);
					if (aLabel == null || aLabel.isEmpty()) {
						aLabel = anAbout;
					}
					e.addCategory(scheme.getAbout(), anAbout, aLabel);
				}
			}
		}
		/**
		 * Add a link to the entry
		 * 
		 * @param href The IRI of the link
		 * @param rel The link rel attribute
		 * @param type The media type of the link
		 * @param hreflang The language of the target
		 * @param length The length of the resource
		 * @return The newly created atom:link
		 * @throws IRISyntaxException if the href is malformed
		    Link addLink(String href, String rel, String type, String title, String hreflang, long length);
		 */
/* rel: This document defines five initial values for the Registry of Link
   Relations:

   1.  The value "alternate" signifies that the IRI in the value of the
       href attribute identifies an alternate version of the resource
       described by the containing element.

   2.  The value "related" signifies that the IRI in the value of the
       href attribute identifies a resource related to the resource
       described by the containing element.  For example, the feed for a
       site that discusses the performance of the search engine at
       "http://search.example.com" might contain, as a child of
       atom:feed:

       <link rel="related" href="http://search.example.com/"/>

       An identical link might appear as a child of any atom:entry whose
       content contains a discussion of that same search engine.

   3.  The value "self" signifies that the IRI in the value of the href
       attribute identifies a resource equivalent to the containing
       element.

   4.  The value "enclosure" signifies that the IRI in the value of the
       href attribute identifies a related resource that is potentially
       large in size and might require special handling.  For atom:link
       elements with rel="enclosure", the length attribute SHOULD be
       provided.

   5.  The value "via" signifies that the IRI in the value of the href
       attribute identifies a resource that is the source of the
       information provided in the containing element.
*/
		SkosHtmlManager skosHtmlManager = this.getSkosHtmlManager(request);
		String askosiRoot = skosHtmlManager.getApplicationRoot(SkosHtmlManager.askosiDisplayApplication);

		ConceptScheme aScheme = aConcept.getInScheme();
		if (aScheme == skosManager.getMetaScheme()) {
			SchemeInterface refScheme = skosManager.openScheme(aConcept.getAbout());
			if (refScheme instanceof NoScheme && (refScheme.getClass() != NoScheme.class)) {
				Collection<ConceptIdentifier> tops = refScheme.getHasTopConcept();			
				param.put("collection", refScheme.getAbout());
				for (ConceptIdentifier anAbout : tops) {
					param.put("entry", anAbout.getAbout());
					String toplink = request.urlFor(TargetType.TYPE_ENTRY, param);
					e.addLink(toplink, "section");
				}
			}
		}

		if (askosiRoot!=null && !askosiRoot.isEmpty()) {
			e.addLink(Util.noMoreSubstitute(Reflector.substituteURL(askosiRoot, aConcept, SkosHtmlManager.askosiDisplayUrl, language)), "alternate");
		}
		String aUrl = aScheme.getDisplayAdapted(skosHtmlManager,aConcept);
		if (aUrl==null || aUrl.isEmpty()) {
			aUrl = aConcept.getUrlAdapted(skosHtmlManager);
		}
		if (aUrl!=null && !aUrl.isEmpty()) {
			e.addLink(Util.noMoreSubstitute(aUrl), "via");
		}
		aUrl = aConcept.getIconAdapted(skosHtmlManager);
		if (aUrl==null || aUrl.isEmpty()) {
			aUrl = aConcept.getInScheme().getIconAdapted(skosHtmlManager);
		}
		if (aUrl!=null && !aUrl.isEmpty()) {
			e.addLink(Util.noMoreSubstitute(aUrl), "icon");
		}
		Object[] params = new Object[3];
		params[1] = aConcept;
		for (ReferringApplication appl : aConcept.getUsage()) {
			String applUsed = appl.getApplication();
			
			for (Count use : appl.getCount()) {
				String aRole = use.getRole();
				if (aRole == null) continue;
				Notation currNotation = null;
				SchemeUsage schemeUsage = aConcept.getInScheme().getApplicationUsage(applUsed,aRole);
				if (schemeUsage == null) continue;
				NoScheme notationScheme = schemeUsage.getInScheme();
				if (notationScheme != null && !notationScheme.equals(aConcept.getInScheme()) && notationScheme instanceof NotationScheme) {
					currNotation = aConcept.getNotation((NotationScheme)notationScheme);
				}
				params[0] = currNotation;
				params[2] = use;
				aUrl = schemeUsage.getSearchURL();
				if (aUrl != null && !aUrl.isEmpty()) {
					e.addLink(Util.noMoreSubstitute(Reflector.substituteURL(askosiRoot, params, aUrl, language)),
							  applUsed+':'+aRole, "text/html",""+use.getCount(),language,-1);
				}
				aUrl = schemeUsage.getAtomURL();
				if (aUrl != null && !aUrl.isEmpty()) {
					e.addLink(Util.noMoreSubstitute(Reflector.substituteURL(askosiRoot, params, aUrl, language)),
							  applUsed+':'+aRole, Constants.FEED_MEDIA_TYPE,""+use.getCount(),language,-1);
				}
			}
		}

		return link;
	}

	@Override
	public String getId(Concept aConcept) throws ResponseContextException {
		if (aConcept == null) return null;
		return (aConcept.getScheme_About());
	}

	@Override
	public String getName(Concept aConcept) throws ResponseContextException {
		if (aConcept == null) return null;
		return (aConcept.getAbout());
	}

	@Override
	public String getTitle(Concept aConcept) throws ResponseContextException {
		if (aConcept == null) return null;
		return aConcept.getPrefLabel(language, false);
	}

	@Override
	public Date getUpdated(Concept aConcept) throws ResponseContextException {
		if (aConcept == null) return null;
		return aConcept.getInScheme().getLastLoaded();
	}

	/*
Concept 	postEntry(String title, IRI id, String summary, Date updated, List<Person> authors, Content content, RequestContext request)
          Create a new entry
	 * @see org.apache.abdera.protocol.server.impl.AbstractEntityCollectionAdapter#postEntry(java.lang.String, org.apache.abdera.i18n.iri.IRI, java.lang.String, java.util.Date, java.util.List, org.apache.abdera.model.Content, org.apache.abdera.protocol.server.RequestContext)
	 */
	public Concept postEntry(String title, IRI id, String summary, Date updated, List<Person> authors, Content content, RequestContext request) {
		ConceptScheme currScheme = null;
		if (scheme instanceof ConceptScheme) {
			currScheme = (ConceptScheme) scheme;
		} else if (scheme instanceof NotationScheme) {
			currScheme = ((NotationScheme)scheme).getPrimary();
		}
		if (currScheme == null) return null;
		Concept aConcept = new Concept(currScheme);
		String entryID = id.getQuery();
		aConcept.setAbout(entryID);
		String language = getUserLanguage(request);
		aConcept.putPrefLabel(language, TermFormat.REGULAR, title, source, Status.New_Proposed);
		aConcept.putScopeNote(language, TermFormat.REGULAR, summary, Status.New_Proposed);
		return aConcept;
	}

	/*
	Update an entry.

	Parameters:
	    entry The entry to update
	    title The new title of the entry
	    updated The new value of atom:updated
	    authors To new listing of authors
	    summary The new summary
	    content The new content
	    request The request context
	 */
	@Override
	public void putEntry(Concept aConcept, String title, Date updated,
			List<Person> authors, String summary, Content content, RequestContext context)
					throws ResponseContextException {
		String language = getUserLanguage(context);
		aConcept.putPrefLabel(language, TermFormat.REGULAR, title, source, Status.New_Proposed);
		aConcept.putScopeNote(language, TermFormat.REGULAR, summary, Status.New_Proposed);
	}

	@Override
	public String getAuthor(RequestContext context)
			throws ResponseContextException {
		LinkedList<String> authors = scheme.getCreator();
		if (authors == null || authors.size() == 0) authors = scheme.getContributor();
		if (authors != null && authors.size() > 0) return authors.getFirst();
		return null;
	}
	/*
	public List<Person> getAuthors(Concept aConcept) {
		LinkedList<String> names = scheme.getCreator();
		LinkedList<Person> authors = new LinkedList<Person>();
		if (names != null) {
			for (String aName : names) {
				Person person = new PersonWrapper();
				person.setName(aName);
				authors.add(person);
			}
		}
		names = scheme.getContributor();
		return authors;
	}
	 */
	public List<Person> getAuthors(Concept aConcept, RequestContext context) throws ResponseContextException {
		Person author = context.getAbdera().getFactory().newAuthor();
		author.setName(getAuthor(context));
		return Arrays.asList(author);
	}

	@Override
	public Object getContent(Concept entry, RequestContext context) {
		Factory factory = context.getAbdera().getFactory();
		Content content = factory.newContent(Content.Type.XML);
		String skosConcept = context.getContextPath()+"/skosconcept.jsp?concept="+entry.getScheme_About();
		content.setSrc(skosConcept);
		return content;
	}

	/*
	 * Get the summary of the entry. By default this returns null.
	 */
	public Text getSummary(Concept aConcept, RequestContext context) {
		if (aConcept == null) return null;
		String language = getUserLanguage(context);
		String aNote = aConcept.getScopeNote(language);
		if (aNote == null || aNote.isEmpty()) return null;
		Text summary = context.getAbdera().getFactory().newSummary(Text.Type.TEXT);
		summary.setText(aNote);
		return summary;
	}

	/*
public CategoriesInfo[]   getCategoriesInfo(RequestContext request)
public ResponseContext  getCategories(RequestContext request)
public ResponseContext  deleteMedia(RequestContext request) {
public ResponseContext  getMedia(RequestContext request) {
public ResponseContext  headMedia(RequestContext request) {
public ResponseContext  optionsMedia(RequestContext request) {
public ResponseContext  putMedia(RequestContext request) {
public ResponseContext  postMedia(RequestContext request) {
public ResponseContext  headEntry(RequestContext request) {
public ResponseContext  optionsEntry(RequestContext request) {
public abstract String  getAuthor(RequestContext request) throws ResponseContextException;
public abstract String  getId(RequestContext request);
public ResponseContext  extensionRequest(RequestContext request) {

	Update a media resource. By default this method is not allowed. Implementations must override this method to support media resource updates

	Parameters:
	    entryObj
	    contentType The mime-type of the media resource
	    slug The value of the Slug request header
	    inputStream An input stream providing access to the request payload
	    request The request context

	    public void putMedia(T entryObj, MimeType contentType, String slug, InputStream inputStream, RequestContext request)

Post a new media resource to the collection. By default, this method is not supported. Implementations must override this method to support posting media resources

Parameters:
    mimeType The mime-type of the resource
    slug The value of the Slug header
    inputStream An InputStream providing access to the request payload
    request The request context

    public T  postMedia(MimeType mimeType, String slug, InputStream inputStream, RequestContext request)

Delete a media resource. By default this method is not supported. Implementations must override this method to support deleting media resources
    public void  deleteMedia(String resourceName, RequestContext request)

Get the name of the media resource. By default this method is unsupported. Implementations must override.
    public String  getMediaName(T entry)

Get an input stream for the media resource. By default this method is unsupported. Implementations must override.
    public InputStream  [More ...] getMediaStream(T entry)

True if this entry is a media-link entry. By default this always returns false. Implementations must override to support media link entries
    public boolean  isMediaEntry(T entry)


	 */

	public NoScheme getScheme() {
		return scheme;
	}

	public void setScheme(NoScheme scheme) {
		this.scheme = scheme;
	}

	@Override
	public List<Concept> getPropertyEntries(RequestContext request,
			Concept aConcept,
			String navigationProperty) throws ResponseContextException {
		int pSep = navigationProperty.indexOf(':');
		if (pSep < 0) {
			pSep = navigationProperty.indexOf('/');
			if (pSep < 0) {
				pSep = navigationProperty.indexOf('_');
			}
		}
		String application = SkosHtmlManager.askosiDisplayApplication;
		String role = navigationProperty;
		if (pSep > 0) {
			application = navigationProperty.substring(0,pSep);
			role = navigationProperty.substring(pSep+1);
		}

		LinkedList<Concept> result = new LinkedList<Concept>();
		if (application.equalsIgnoreCase(SkosHtmlManager.askosiDisplayApplication)) { // Local property ?
			if (aConcept.getInScheme() == skosManager.getMetaScheme() && role.equalsIgnoreCase("topConcepts")) {
				SchemeInterface refScheme = skosManager.openScheme(aConcept.getAbout());
				if (refScheme instanceof NoScheme && (refScheme.getClass() != NoScheme.class)) {
					Collection<ConceptIdentifier> tops = refScheme.getHasTopConcept();
					for (ConceptIdentifier aTop : tops) {
						result.add(refScheme.getConcept(aTop.getAbout()));
					}
				} 
			} else {
				LinkedList<Value> content = new LinkedList<Value>();
				Predicate currProp = Predicate.findInConcept(role);
				try {
					 content = (LinkedList<Value>) Predicate.getAll[currProp.ordinal()].invoke(aConcept);
				} catch (IllegalArgumentException e) {
					log.error("Bad concept property: "+role+", "+e.toString());
				} catch (IllegalAccessException e) {
					log.error("Bad concept property: "+role+", "+e.toString());
				} catch (InvocationTargetException e) {
					log.error("Bad concept property: "+role+", "+e.toString());
				}
				if (content != null) {
					for (Value aResource: content) {
						if (aResource==null) continue;
						String about = aResource.stringValue();
						int pColon = about.indexOf(':');
						if (pColon >= 0) about = about.substring(pColon+1);
						Concept toAdd = skosManager.getScheme_Concept(about, this.scheme, null, null);
						if (toAdd == null) {
							log.error("Concept "+aResource.stringValue()+" not found.");
						} else {
							result.add(toAdd);
						}
					}
				}
			}
		} else { // other applications which returns CONCEPTS ???
			log.error("Is this application_role really returns ASKOSI Concepts. If yes, yet to be implemented! "+application+'_'+role);
		}
		return result;
	}
	
	@Override
	public List<Concept> getCommaSeparatedEntriesList(RequestContext request, String entriesList) {
		StringTokenizer st = new StringTokenizer(entriesList,",; \t");
		TreeSet<String> sorter = new TreeSet<String>();
		while (st.hasMoreTokens()) {
			sorter.add(st.nextToken());
		}
		LinkedList<Concept> result = new LinkedList<Concept>();
		for (String oneAbout: sorter) {
			Concept toAdd = skosManager.getScheme_Concept(oneAbout, this.scheme, null, null);
			if (toAdd == null) {
				log.error("Concept "+oneAbout+" (from entries list) not found in "+this.scheme.getAbout());
			} else {
				result.add(toAdd);
			}
		}
		return result;
	}

	@Override
	public String getIconUrl(RequestContext request) {
		SkosHtmlManager skosHtmlManager = this.getSkosHtmlManager(request);
		String aUrl = scheme.getIconAdapted(skosHtmlManager);
		return Util.noMoreSubstitute(aUrl);
	}
}
